package com.yumeng.framework.auth.service.impl;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.yumeng.common.api.ApiCode;
import com.yumeng.common.data.constants.LogTopic;
import com.yumeng.common.helper.AssertHelper;
import com.yumeng.framework.auth.bean.LoginTokenInfo;
import com.yumeng.framework.auth.bean.TokenInfo;
import com.yumeng.framework.auth.bean.VerifyLoginInfo;
import com.yumeng.common.auth.BaseAuthInfo;
import com.yumeng.framework.auth.cache.TokenCacheHelper;
import com.yumeng.framework.auth.cache.info.AuthUserCacheInfo;
import com.yumeng.framework.auth.cache.info.TokenCacheInfo;
import com.yumeng.framework.auth.data.enums.JwtType;
import com.yumeng.framework.auth.data.enums.LogoutType;
import com.yumeng.framework.auth.exception.TokenAuthException;
import com.yumeng.framework.auth.helper.HttpTokenHelper;
import com.yumeng.framework.auth.jwt.JwtUtils;
import com.yumeng.framework.auth.service.TokenService;
import com.yumeng.config.helper.PropHelper;
import com.yumeng.config.properties.YumengAuthProperties;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.Map;

/**
 * token处理接口（JWT）
 *
 * @author wxd
 * @date 2025/9/24 13:36
 */
@Slf4j(topic = LogTopic.TOKEN_FILE)
public class JwtTokenServiceImpl implements TokenService {

    private TokenCacheHelper tokenCacheHelper;
    @Autowired
    public void setTokenCacheHelper(TokenCacheHelper tokenCacheHelper) {
        this.tokenCacheHelper = tokenCacheHelper;
    }

    private static YumengAuthProperties.JwtProperties getJwtProperties(){
        return PropHelper.getJwtProperties();
    }

    @Override
    public LoginTokenInfo generateTokenForLogin(BaseAuthInfo authInfo, JwtType jwtType, Map<String, Object> loginInfo) {
        LoginTokenInfo tokenInfo = JwtUtils.generateTokenForLogin(authInfo, jwtType, authInfo.getLoginInfo());
        tokenCacheHelper.cacheTokenAndAuthInfoForLogin(tokenInfo, authInfo);
        return tokenInfo;
    }

    @Override
    public void clearTokenForLogout(LogoutType type) {
        if (LogoutType.CURR_SESSION.equals(type)){
            tokenCacheHelper.clearCacheInfoForCurrSession();
        }else if (LogoutType.CURR_USER.equals(type)){
            tokenCacheHelper.clearCacheInfoForCurrUser();
        }else if (LogoutType.OTHER_SESSION.equals(type)){
            tokenCacheHelper.clearCacheInfoForOtherSession();
        }else{
            throw new TokenAuthException("注销类型异常");
        }
    }

    /**
     * tokenInfo预验证
     *
     * @param tokenInfo
     * @param response
     * @return
     */
    @Override
    public VerifyLoginInfo preVerifyTokenInfo(TokenInfo tokenInfo, HttpServletResponse response) {
        VerifyLoginInfo info = new VerifyLoginInfo();
        String token = tokenInfo.getToken();
        String refreshToken = tokenInfo.getRefreshToken();
        DecodedJWT decodedJWT = JwtUtils.getDecodedJWT(token);
        JwtType jwtType = JwtUtils.getJwtType(decodedJWT);
        //请勿携带RefreshToken
        boolean doNotCarryRefreshToken = false;

        //如果开启缓存验证或者设置单用户登录，则必须开启jwt缓存
        if ((getJwtProperties().getCacheCheck() || getJwtProperties().getSingleLogin()) && !tokenCacheHelper.isTokenCachingEnabled()){
            throw new TokenAuthException("如需缓存登录验证或者控制单个用户登录，请开启JWT缓存");
        }

        //0、判断是否超出最长登录时间，默认31天
        Date loginTime = JwtUtils.getLoginTime(decodedJWT);
        AssertHelper.assertNotNull(loginTime, "verifyToken loginTime is null");
        Date expireDate = DateUtils.addSeconds(loginTime, getJwtProperties().getMaxLoginSecond());
        if (expireDate.before(new Date())){
            throw new TokenAuthException("token已过期：最长登录时间").setApiCode(ApiCode.AUTH_EXPIRED);
        }

        //jwt是否已超时
        boolean isExpired = JwtUtils.isExpired(decodedJWT);
        if (!JwtUtils.refreshTokenEnable()){//是否启用jwt刷新机制
            if (isExpired){
                throw new TokenAuthException("认证已过期").setApiCode(ApiCode.AUTH_EXPIRED);
            }
            doNotCarryRefreshToken = true;
        }else{
            //是否需要RefreshToken流程
            boolean doProcessRefreshToken = JwtUtils.needRefreshToken(jwtType);
            //1、验证refreshToken
            boolean needRefreshToken = isExpired && doProcessRefreshToken;
            if (needRefreshToken){
                if (StringUtils.isBlank(refreshToken)){
                    throw new TokenAuthException("请携带refresh_token").setApiCode(ApiCode.TOKEN_INVALID);
                }else{
                    //验证refreshToken有效性
                    JwtUtils.checkRefreshCode(decodedJWT, refreshToken);
                }
            }
            //2、判断是否重复更新，不管是否重复更新，都需先验证refreshToken
            //checkRepeatRefresh(token, response);
            //3、当jwt已超时
            if (isExpired){
                if (doProcessRefreshToken){//如果执行RefreshToken流程
                    info.setNeedRefresh(true);
                }else {
                    throw new TokenAuthException("登录已超时").setApiCode(ApiCode.AUTH_EXPIRED);
                }
            }
            //4、当jwt进入刷新倒计时
            else if (isNeedRefresh(decodedJWT)){
                info.setNeedRefresh(true);
                doNotCarryRefreshToken = true;
            }else {
                doNotCarryRefreshToken = true;
            }
        }
        if (doNotCarryRefreshToken && StringUtils.isNotBlank(refreshToken)){
            throw new TokenAuthException("请勿携带更新凭证");
        }

        //4、验证缓存中jwt信息
        if (tokenCacheHelper.isTokenCachingEnabled() && !isExpired){
            if (getJwtProperties().getCacheCheck() || getJwtProperties().getSingleLogin()){
                TokenCacheInfo tokenCacheInfo = tokenCacheHelper.getTokenCacheInfo(token);
                if (tokenCacheInfo == null){
                    throw new TokenAuthException("Token信息已被删除，请重新登录").setApiCode(ApiCode.UNAUTHORIZED);
                }
                AuthUserCacheInfo userCacheInfo = tokenCacheHelper.getAuthUserCacheInfo(tokenCacheInfo.getIdentityId());
                if (userCacheInfo == null){
                    tokenCacheHelper.clearCacheInfoForCurrSession();
                    throw new TokenAuthException("AuthUser信息已被删除，请重新登录").setApiCode(ApiCode.UNAUTHORIZED);
                }
            }
        }

        info.setSucc(true);
        return info;
    }

    /**
     * 刷新token
     *
     * @param oldTokenInfo
     * @param info
     * @param response
     */
    @Override
    public void refreshToken(TokenInfo oldTokenInfo, VerifyLoginInfo info, HttpServletResponse response) {
        //是否启用token刷新机制
        if (!JwtUtils.refreshTokenEnable()) {
            return;
        }
        if (!info.isNeedRefresh()){
            return;
        }
        String jwt = oldTokenInfo.getToken();
        BaseAuthInfo authInfo = getAuthInfoByToken(jwt, true);
        TokenInfo tokenInfo = JwtUtils.refreshToken(authInfo, oldTokenInfo);
        //tokenInfo可能会发生变化，如果之前已有对应的新token，则返回已存在的。
        //如：同一个页面，多个接口同时调用，同时触发刷新流程
        tokenInfo = tokenCacheHelper.refreshTokenCache(jwt, tokenInfo, authInfo);
        if (tokenInfo == null){
            //返回null，说明不需要刷新token
            return;
        }
        HttpTokenHelper.refreshHeaderToken(tokenInfo, ApiCode.SUCCESS, response);
    }

    /**
     * 根据jwt，获取AuthInfo（基于缓存）
     *
     * @param token
     * @return
     */
    @Override
    public BaseAuthInfo getAuthInfoByToken(String token, boolean strict) {
        DecodedJWT decodedJWT = JwtUtils.getDecodedJWT(token);
        String identityId = JwtUtils.getIdentityId(decodedJWT);
        String authKey = JwtUtils.getAuthKey(decodedJWT);
        BaseAuthInfo authInfo = tokenCacheHelper.getAuthUserCacheInfo(identityId);
        if (strict && authInfo == null){
            throw new TokenAuthException("AuthUser认证信息缺失").setApiCode(ApiCode.UNAUTHORIZED);
        }
        if (authInfo != null){
            AssertHelper.assertEquals(identityId, authInfo.getIdentityId(), "getAuthInfoByToken 身份ID不匹配");
            AssertHelper.assertEquals(authKey, authInfo.getAuthKey(), "getAuthInfoByToken 认证Key不匹配");
        }
        return authInfo;
    }


    /**
     * 判断jwt是否需要刷新
     * @param decodedJWT
     * @return
     */
    private boolean isNeedRefresh(DecodedJWT decodedJWT){
        if(JwtUtils.isExpired(decodedJWT)){
            throw new TokenAuthException("jwt已超时").setApiCode(ApiCode.AUTH_EXPIRED);
        }
        //获取过期时间
        Date expireDate = JwtUtils.getExpireDate(decodedJWT);
        JwtType jwtType = JwtUtils.getJwtType(decodedJWT);
        //获取倒计时
        Integer countdown;
        if (JwtType.PC.equals(jwtType)){
            countdown = getJwtProperties().getRefreshCountdown();//默认10分钟
        }else if (JwtType.MOBILE.equals(jwtType)){
            countdown = getJwtProperties().getRefreshCountdown();//默认10分钟
        }else if (JwtType.REMOTE.equals(jwtType)){
            countdown = getJwtProperties().getRefreshCountdownRemote();//默认60分钟
        }else{
            throw new TokenAuthException("判断jwt是否需要刷新 JwtType异常");
        }
        return DateUtils.addSeconds(new Date(), countdown).after(expireDate);
    }

    /**
     * 判断是否重复刷新jwt
     * 如果是，根据oldJwt获取之前生成的新tokenInfo。返回新tokenInfo。状态码：4061，不返回业务数据。
     * @param oldJwt
     */
/*    private void checkRepeatRefresh(String oldJwt, HttpServletResponse response){
        TokenInfo existsInfo = tokenCacheHelper.checkExistsTokenInfo(oldJwt);
        if (existsInfo != null){
            if (tokenCacheHelper.isTokenCachingEnabled()){//只有开启缓存的情况下，才能获取到刷新的tokenInfo
                log.info("验证token，重复jwt，原token:{}，使用token：{}，使用refreshToken:{}。", oldJwt, existsInfo.getToken(), existsInfo.getRefreshToken());
                HttpTokenHelper.refreshHeaderToken(existsInfo, response);
            }else {
                log.info("验证token，jwt重复刷新，token:{}", oldJwt);
            }
            throw new TokenAuthException("token已失效，请使用更新的token");
        }
    }*/

}
